<?php
/**
 * Created by PhpStorm.
 * User: 85210755@qq.com
 * NickName: 柏宇娜
 * Date: 2019/5/31 16:16
 */

namespace lib;

use think\facade\Db;
use QL\QueryList;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;

class Server
{
    private $serv;
    private $book_id;
    private $book_name;
    private $log;
    private $redis_conf;
    private $redis;
    private $ql_option = [
        'max'             => 5,
        'timeout'         => 10,
        'connect_timeout' => 5,
        'delay'           => 500,
        'strict'          => false,
        'verify'          => false,
        'referer'         => true,
        'protocols'       => ['https'],
        'track_redirects' => false,
        'headers'         => [
            'Connection'                => 'keep-alive',
            'Cache-Control'             => 'max-age=0',
            'Upgrade-Insecure-Requests' => '1',
            'Referer'                   => 'https://www.baidu.com/',
            'Accept'                    => 'application/json, text/plain, */*',
            'Accept-Encoding'           => 'gzip, deflate, sdch, br',
            'Cookie'                    => 'GUID=a3e1bf9a-2104-7f28-5975-36fdc3d3bce5; UM_distinctid=16a59b292e84da-0eaa4b47faa91c-3e7e045d-100200-16a59b292e9778; Hm_lvt_b33ccadfca0f88b71e592ec2bf9d3497=1559367681; Hm_lpvt_b33ccadfca0f88b71e592ec2bf9d3497=1559367681; CNZZDATA1257018823=1312955637-1559364831-https%253A%252F%252Fwww.17k.com%252F%7C1559364831; CNZZDATA5647345=cnzz_eid%3D3893204-1554477150-https%253A%252F%252Fwww.baidu.com%252F%26ntime%3D1559379383; c_channel=0; c_csc=web; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2271945827%22%2C%22%24device_id%22%3A%2216a59b291843aa-011f266dd88914-3e7e045d-1049088-16a59b29185568%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E5%BC%95%E8%8D%90%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22https%3A%2F%2Fgraph.qq.com%2Foauth2.0%2Fshow%3Fwhich%3DLogin%26display%3Dpc%26response_type%3Dcode%26client_id%3D215314%26scope%3Dget_user_info%26redirect_uri%3Dhttps%253A%252F%252Fpassport.17k.com%252Fsns%252FqqCallback.action%253FfromUrl%253Dhttp%22%2C%22%24latest_referrer_host%22%3A%22graph.qq.com%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC%22%7D%2C%22first_id%22%3A%22a3e1bf9a-2104-7f28-5975-36fdc3d3bce5%22%7D; Hm_lvt_9793f42b498361373512340937deb2a0=1559229077,1559382123; accessToken=id%3D71945827%26nickname%3D%25E6%259F%258F%25E5%25AE%2587%25E5%25A8%259C%26avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F07%252F27%252F58%252F71945827.jpg%26e%3D1574935769%26s%3D41a4d8aab05f560e; Hm_lpvt_9793f42b498361373512340937deb2a0=1559367681',
            'User-Agent'                => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
        ],
    ];

    public function __construct($redis_conf, $id, $log)
    {
        $this->book_id    = $id;
        $this->log        = $log;
        $this->redis_conf = $redis_conf;
        $this->serv       = new \swoole_server("127.0.0.1", 9501);
        $this->serv->set([
            'daemonize'     => 0,//守护进程
            'worker_num'    => 1, //开启任务数
            'max_request'   => 200,
            'dispatch_mode' => 3,//进程数据包分配模式 1平均分配，2按FD取摸固定分配，3抢占式分配
        ]);
        $this->serv->on('WorkerStart', [$this, 'onWorkerStart']);
        $this->serv->on('Receive', function ($server, $fd, $reactor_id, $data) {
            $this->log->info('onReceive..' . json_encode($data, JSON_UNESCAPED_UNICODE));
        });

        $this->serv->start();
    }

    public function onWorkerStart($serv, $worker_id)
    {
        if (!$this->redis) {
            $this->redis = new \Redis();
            $this->redis->connect($this->redis_conf['host'], $this->redis_conf['port']);
            if ($this->redis_conf['auth']) {
                $this->redis->auth($this->redis_conf['auth']);
            }
            if ($this->redis_conf['dbindex']) {
                $this->redis->select($this->redis_conf['dbindex']);
            }
        }
        $this->book_name = Db::name('novel')->where('nid', $this->book_id)->value('title');
        while ($url = $this->redis->rpoplpush($this->book_id, 'finishi' . $this->book_id)) {
            try {
                $this->pickup($url, $this->redis->llen('finishi' . $this->book_id));
            } catch (\Exception $e) {
                $this->redis->lPush("errorlist" . $this->book_id, $url);
                $this->log->error("采集失败:" . $url . PHP_EOL . $e->getMessage());
            }
        }

        if ($this->redis->lRange('errorlist' . $this->book_id, 0, -1)) {
            while ($url = $this->redis->rpoplpush($this->book_id, 'finishi' . $this->book_id)) {
                try {
                    $this->pickup($url, $this->redis->llen('finishi' . $this->book_id));
                } catch (\Exception $e) {
                    $this->redis->lPush("errorlist" . $this->book_id, $url);
                    $this->log->error("采集retry失败:" . $url . PHP_EOL . $e->getMessage());
                }
            }
        }

        $this->serv->after(5000, function () use ($worker_id) {
            if ($this->redis->lLen("errorlist" . $this->book_id) > 0) {
                $this->redis->close();
                $this->redis = null;
                $this->log->info('进程ID：' . $worker_id . '结束; 任务失败退出,swoole关闭.');
                $this->serv->shutdown();
            }

            if ($this->redis && !$this->redis->get('lock')) {
                $this->redis->set('lock', true);
                $this->afterTask();

                if ($this->redis) {
                    $this->redis->del('finishi' . $this->book_id);
                    $this->redis->del('lock');
                    $this->redis->close();
                    $this->redis = null;
                }

                $this->log->info('the last进程ID：' . $worker_id . '结束; 任务结束,swoole关闭.');
                $this->serv->shutdown();
            }

            $this->log->info('进程ID：' . $worker_id . '结束');
        });
    }

    public function afterTask()
    {
        $list  = Db::name('novel_section')->where('nid', $this->book_id)->order('section_no ASC')->field('sid,section_no')->select();
        $count = count($list);
        foreach ($list as $index => $item) {
            $data = ['prev' => 0];
            if ($index > 0) {
                $data['prev'] = $list[$index - 1]['sid'];
            }
            if ($index + 1 == $count) {
                $data['next'] = 0;
            } else {
                $data['next'] = $list[$index + 1]['sid'];
            }
            Db::name('novel_section')->where('sid', $item['sid'])->update($data);
        }
        Db::name('novel')->where('nid', $this->book_id)->update(['last_section' => $item['sid'], 'last_section_title' => $item['section_title']]);
    }

    public function pickup($url, $no)
    {
        if (strpos($url, 'vip@') !== false) {
            $url = str_replace('vip@', '', $url);
            $url = str_replace('/chapter/', '', $url);
            $url = str_replace('.html', '', $url);
            list($book_id, $chapter_id) = explode('/', $url);
            $url = 'https://www.17k.com/ck/book/' . $book_id . '/' . 'chapter/' . $chapter_id . '?subAllPrice=1&appKey=2406394919';
            $c1  = [
                'CNZZDATA5647345'           => 'cnzz_eid%3D956176011-1559531113-https%253A%252F%252Fwww.17k.com%252F%26ntime%3D1559531113',
                'UM_distinctid'             => '16b1b7a96ec299-0ae4a69ef76aa7-39395704-1fa400-16b1b7a96edd7',
                'accessToken'               => 'id%3D71945827%26nickname%3D%25E6%259F%258F%25E5%25AE%2587%25E5%25A8%259C%26avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F07%252F27%252F58%252F71945827.jpg%26e%3D1575086157%26s%3D5a6e1325f0dfec33',
                'c_csc'                     => 'web',
                'sensorsdata2015jssdkcross' => '%7B%22distinct_id%22%3A%2271945827%22%2C%22%24device_id%22%3A%2216b1b7a96fc591-0c71ff3751bae3-39395704-2073600-16b1b7a96fd6ec%22%2C%22props%22%3A%7B%7D%2C%22first_id%22%3A%226281ad98-96b6-a023-1907-7c1c661cb2fe%22%7D',
            ];

            $jar      = CookieJar::fromArray($c1, 'www.17k.com');
            $client   = new Client(['cookies' => $jar]);
            $response = $client->request('GET', $url, ['verify' => false]);

            $arr = json_decode($response->getBody()->getContents(), true);

            $section_info = explode(' ', $arr['data']['name']);
            $chinese_no   = $section_info[0];
            $tit          = $section_info[1];
            $time         = substr($arr['data']['publishDate'], 0, 10);
            $content      = $arr['data']['content'][0]['text'];
        } else {
            //随机UA
            //$this->ql_option['User-Agent'] = $this->randUserAgent('pc');
            //启用代理
            //$proxy                         = $this->randProxy();
            //if ($proxy) {
            //    $this->ql_option['proxy'] = $proxy;
            //}
            $ql      = QueryList::get($url, $this->ql_option);
            $content = $ql->find('#readArea > .content > .p')
                ->removeClass('copy')->removeClass('qrcode')->removeClass('chapter_text_ad')->remove('#banner_content')
                ->text();
            if (!$content) {
                $this->log->error('获取內容失败' . $url);
                return;
            }
            //$ql->find('.area > infoPath > a')->eq(2)->text();
            $content      = str_replace('本书首发来自17K小说网，第一时间看正版内容！', '', $content);
            $content      = $content = str_replace('下载17K客户端，《' . $this->book_name . '》最新章节无广告纯净阅读。', '', $content);
            $content      = str_replace('17K客户端专享，签到即送VIP，免费读全站。', '', $content);
            $section_info = $ql->find('#readArea h1')->text();
            $chinese_no   = trim(substr($section_info, 0, stripos($section_info, ' ')));
            $tit          = trim(substr($section_info, stripos($section_info, ' ')));
            $time         = strtotime(substr(trim($ql->find('#readArea .chapter_update_time')->text()), -19));
        }

        $row = [
            'nid'                => $this->book_id,
            'section_no'         => $no,
            'section_chinese_no' => $chinese_no,
            'section_title'      => $tit,
            'add_time'           => $time
        ];
        $sid = Db::name('novel_section')->insertGetId($row);
        if (!$sid) {
            $this->log->error('pickup写入数据库失败:' . $url);
        }
        $path = __DIR__ . '/../files/' . $this->book_id;
        if (!is_dir($path)) {
            mkdir($path, 0775);
        }
        $file = $path . DIRECTORY_SEPARATOR . $sid . '.txt';
        if (!file_put_contents($file, $content)) {
            $this->log->error('pickup写入内容失败:' . $url);
        }
    }

    public function randUserAgent($type = 'pc')
    {
        $userAgentArray = [
            'pc'      => [
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
                'Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0',
                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:29.0) Gecko/20100101 Firefox/29.0',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14',
                'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0',
                'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36',
                'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',
                'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
                'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0)',
                'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
                'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko',
            ],
            'android' => [
                'Mozilla/5.0 (Android; Mobile; rv:29.0) Gecko/29.0 Firefox/29.0',
                'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36',
            ],
            'ios'     => [
                'Mozilla/5.0 (iPad; CPU OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) CriOS/34.0.1847.18 Mobile/11B554a Safari/9537.53',
                'Mozilla/5.0 (iPad; CPU OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53',
                'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4',
                'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4',
            ],
        ];
        switch ($type) {
            case 'pc':
                return $userAgentArray['pc'][array_rand($userAgentArray['pc'])] . rand(0, 10000);
                break;
            case 'android':
                return $userAgentArray['android'][array_rand($userAgentArray['android'])] . rand(0, 10000);
                break;
            case 'ios':
                return $userAgentArray['ios'][array_rand($userAgentArray['ios'])] . rand(0, 10000);
                break;
            case 'mobile':
                $userAgentArray = array_merge($userAgentArray['android'], $userAgentArray['ios']);
                return $userAgentArray[array_rand($userAgentArray)] . rand(0, 10000);
            default:
                return $type;
                break;
        }
    }

    public function randProxy()
    {
        if (!$this->redis->exists('proxy')) {
            $baseuri = 'http://www.89ip.cn/index_';
            for ($j = 1; $j < 4; $j++) {
                $proxy = QueryList::get($baseuri . $j . '.html')->find('.layui-table > tbody > tr')->map(function ($tr) {
                    return $tr->find('td')->texts();
                })->all();

                for ($i = 1; $i <= 15; $i++) {
                    if (stream_socket_client(trim($proxy[$i][0]) . ":" . trim($proxy[$i][1]), $errno, $errstr, 1)) {
                        $this->redis->sadd('proxy', trim($proxy[$i][0]) . ':' . trim($proxy[$i][1]));
                    }
                }
            }
        } else {
            foreach ($this->redis->smembers('proxy') as $v) {
                if (!stream_socket_client($v, $errno, $errstr, 1)) {
                    $this->redis->srem('proxy', $v);
                }
            }
        }

        $proxy = $this->redis->srandmember('proxy');
        if ($proxy) return 'tcp://' . $proxy;
        $this->log->error('没有可用代理.');
        return false;
    }
}